package org.cloud.mybatisplus.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.github.pagehelper.Page;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.cloud.mybatisplus.annotation.Query;
import org.cloud.utils.CollectionUtil;
import org.springframework.util.ObjectUtils;

@Slf4j
public class MyBatisPlusUtil {

    private MyBatisPlusUtil() {

    }

    private final static MyBatisPlusUtil myBatisPlusUtil = new MyBatisPlusUtil();

    public static MyBatisPlusUtil single() {
        return myBatisPlusUtil;
    }

    public <T> QueryWrapper<T> createQueryWrapperByParams(Map<String, Object> params) {
        QueryWrapper<T> queryWrapper = Wrappers.emptyWrapper();
        params.forEach((column, value) -> {
            List<?> listValue = null;
            if (value == null) {
                queryWrapper.isNotNull(column);
            } else if (value instanceof Object[]) {
                listValue = Arrays.asList((Object[]) value);
            } else if (value instanceof List<?>) {
                listValue = (List<?>) value;
            } else {
                queryWrapper.eq(column, value);
            }
            if (listValue == null) {
                return;
            }
            if (listValue.size() == 1) {
                queryWrapper.eq(column, value);
                return;
            }
            String[] columns = column.split("::");
            if (columns.length > 1) {
                if ("BETWEEN".equalsIgnoreCase(columns[1])) {
                    queryWrapper.between(column, listValue.get(0), listValue.get(1));
                } else {
                    queryWrapper.in(column, listValue);
                }
            } else {
                queryWrapper.in(column, listValue);
            }
        });
        return queryWrapper;
    }


    public <R, Q> QueryWrapper<R> getPredicate(Q query) {
        QueryWrapper<R> queryWrapper = Wrappers.query();
        if (query == null) {
            return queryWrapper;
        }
        try {
            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
            for (Field field : fields) {
                boolean accessible = field.canAccess(query);
                field.setAccessible(true);
                Query q = field.getAnnotation(Query.class);
                if (q == null) {
                    if (CollectionUtil.single().isNotEmpty(field.get(query))) {
                        queryWrapper.eq(humpToUnderline(field.getName()), field.get(query));
                    }
                    continue;
                }
                if (q.ignore()) {  // 如果为忽略状态，那么跳过
                    continue;
                }
                String propName = q.propName();
                String blurry = q.blurry();
                String attributeName = humpToUnderline(isBlank(propName) ? field.getName() : propName);
                Object val = field.get(query);
                if (ObjectUtils.isEmpty(val)) {
                    continue;
                }
                // 模糊多字段
                if (!ObjectUtils.isEmpty(blurry)) {
                    String[] blurrys = blurry.split(",");
                    //queryWrapper.or();
                    queryWrapper.and(wrapper -> {
                        for (String s : blurrys) {
                            wrapper.or();
                            wrapper.like(humpToUnderline(s), val.toString());
                        }
                    });
                    continue;
                }
                switch (q.type()) {
                    case EQUAL:
                        //queryWrapper.and(wrapper -> wrapper.eq(finalAttributeName, val));
                        queryWrapper.eq(attributeName, val);
                        break;
                    case GREATER_THAN:
                        queryWrapper.ge(attributeName, val);
                        break;
                    case GREATER_THAN_NQ:
                        queryWrapper.gt(attributeName, val);
                        break;
                    case LESS_THAN:
                        queryWrapper.le(attributeName, val);
                        break;
                    case LESS_THAN_NQ:
                        queryWrapper.lt(attributeName, val);
                        break;
                    case INNER_LIKE:
                        queryWrapper.like(attributeName, val);
                        break;
                    case LEFT_LIKE:
                        queryWrapper.likeLeft(attributeName, val);
                        break;
                    case RIGHT_LIKE:
                        queryWrapper.likeRight(attributeName, val);
                        break;
                    case IN:
                        if (CollectionUtil.single().isNotEmpty(val)) {
                            Collection<?> collectionIn = (Collection<?>) val;
                            queryWrapper.in(attributeName, collectionIn);
                        }
                        break;
                    case NOT_EQUAL:
                        queryWrapper.ne(attributeName, val);
                        break;
                    case NOT_NULL:
                        queryWrapper.isNotNull(attributeName);
                        break;
                    case BETWEEN:
                        if ((val instanceof List) && CollectionUtil.single().isNotEmpty(val)) {
                            List<?> between = (List<?>) val;
                            queryWrapper.between(attributeName, between.get(0), between.get(1));
                        }
                        break;
                    case UNIX_TIMESTAMP:
                        if ((val instanceof List) && CollectionUtil.single().isNotEmpty(val)) {
                            List<?> UNIX_TIMESTAMP = (List<?>) val;
                            if (!UNIX_TIMESTAMP.isEmpty()) {
                                SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                if (UNIX_TIMESTAMP.size() == 1 || (UNIX_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(UNIX_TIMESTAMP.get(1)))) {
                                    Date time1 = fm.parse(UNIX_TIMESTAMP.get(0).toString());
                                    queryWrapper.ge(attributeName, time1);
                                } else if (UNIX_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(UNIX_TIMESTAMP.get(0))) {
                                    Date time2 = fm.parse(UNIX_TIMESTAMP.get(1).toString());
                                    queryWrapper.le(attributeName, time2);
                                } else {
                                    Date time1 = fm.parse(UNIX_TIMESTAMP.get(0).toString());
                                    Date time2 = fm.parse(UNIX_TIMESTAMP.get(1).toString());
                                    queryWrapper.between(attributeName, time1, time2);
                                }
                            }
                        }
                        break;
                    case LONG_TIMESTAMP:
                        if ((val instanceof List) && CollectionUtil.single().isNotEmpty(val)) {
                            List<?> LONG_TIMESTAMP = (List<?>) val;
                            if (!LONG_TIMESTAMP.isEmpty()) {
                                if (LONG_TIMESTAMP.size() == 1 || (LONG_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(LONG_TIMESTAMP.get(1)))) {
                                    Date time1 = new Date(Long.parseLong(LONG_TIMESTAMP.get(0).toString()));
                                    queryWrapper.ge(attributeName, time1);
                                } else if (LONG_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(LONG_TIMESTAMP.get(0))) {
                                    Date time2 = new Date(Long.parseLong(LONG_TIMESTAMP.get(1).toString()));
                                    queryWrapper.le(attributeName, time2);
                                } else {
                                    Date time1 = new Date(Long.parseLong(LONG_TIMESTAMP.get(0).toString()));
                                    Date time2 = new Date(Long.parseLong(LONG_TIMESTAMP.get(1).toString()));
                                    queryWrapper.between(attributeName, time1, time2);
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
                field.setAccessible(accessible);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return queryWrapper;
    }

    public <R, Q> LambdaQueryWrapper<R> getLambdaPredicate(Q query, Class<R> cls) {
        LambdaQueryWrapper<R> queryWrapper = Wrappers.lambdaQuery();
        if (query == null) {
            return queryWrapper;
        }
        try {
            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
            for (Field field : fields) {
                boolean accessible = field.canAccess(query);
                field.setAccessible(true);
                Query q = field.getAnnotation(Query.class);
                if (q == null) {
                    if (CollectionUtil.single().isNotEmpty(field.get(query))) {
                        queryWrapper.eq(fieldNameToSFunction(cls, field.getName()), field.get(query));
                    }
                    continue;
                }
                if (q.ignore()) {  // 如果为忽略状态，那么跳过
                    continue;
                }
                String propName = q.propName();
                String blurry = q.blurry();
                String attributeName1 = isBlank(propName) ? field.getName() : propName;
                SFunction<R, Q> sFunction = fieldNameToSFunction(cls, attributeName1);
                Object val = field.get(query);
                if (ObjectUtils.isEmpty(val)) {
                    continue;
                }
                // 模糊多字段
                if (!ObjectUtils.isEmpty(blurry)) {
                    String[] blurrys = blurry.split(",");
                    //queryWrapper.or();
                    queryWrapper.and(wrapper -> {
                        for (String s : blurrys) {
                            wrapper.or();
                            wrapper.like(fieldNameToSFunction(cls, s), val.toString());
                        }
                    });
                    continue;
                }

                switch (q.type()) {
                    case EQUAL:
                        //queryWrapper.and(wrapper -> wrapper.eq(finalAttributeName, val));
                        queryWrapper.eq(sFunction, val);
                        break;
                    case GREATER_THAN:
                        queryWrapper.ge(sFunction, val);
                        break;
                    case GREATER_THAN_NQ:
                        queryWrapper.gt(sFunction, val);
                        break;
                    case LESS_THAN:
                        queryWrapper.le(sFunction, val);
                        break;
                    case LESS_THAN_NQ:
                        queryWrapper.lt(sFunction, val);
                        break;
                    case INNER_LIKE:
                        queryWrapper.like(sFunction, val);
                        break;
                    case LEFT_LIKE:
                        queryWrapper.likeLeft(sFunction, val);
                        break;
                    case RIGHT_LIKE:
                        queryWrapper.likeRight(sFunction, val);
                        break;
                    case IN:
                        if (CollectionUtil.single().isNotEmpty(val)) {
                            Collection<?> collectionIn = (Collection<?>) val;
                            queryWrapper.in(sFunction, collectionIn);
                        }
                        break;
                    case NOT_EQUAL:
                        queryWrapper.ne(sFunction, val);
                        break;
                    case NOT_NULL:
                        queryWrapper.isNotNull(sFunction);
                        break;
                    case BETWEEN:
                        if ((val instanceof List) && CollectionUtil.single().isNotEmpty(val)) {
                            List<?> between = (List<?>) val;
                            queryWrapper.between(sFunction, between.get(0), between.get(1));
                        }
                        break;
                    case UNIX_TIMESTAMP:
                        if ((val instanceof List) && CollectionUtil.single().isNotEmpty(val)) {
                            List<?> UNIX_TIMESTAMP = (List<?>) val;
                            if (!UNIX_TIMESTAMP.isEmpty()) {
                                SimpleDateFormat fm = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                                if (UNIX_TIMESTAMP.size() == 1 || (UNIX_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(UNIX_TIMESTAMP.get(1)))) {
                                    Date time1 = fm.parse(UNIX_TIMESTAMP.get(0).toString());
                                    queryWrapper.ge(sFunction, time1);
                                } else if (UNIX_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(UNIX_TIMESTAMP.get(0))) {
                                    Date time2 = fm.parse(UNIX_TIMESTAMP.get(1).toString());
                                    queryWrapper.le(sFunction, time2);
                                } else {
                                    Date time1 = fm.parse(UNIX_TIMESTAMP.get(0).toString());
                                    Date time2 = fm.parse(UNIX_TIMESTAMP.get(1).toString());
                                    queryWrapper.between(sFunction, time1, time2);
                                }
                            }
                        }
                        break;
                    case LONG_TIMESTAMP:
                        if ((val instanceof List) && CollectionUtil.single().isNotEmpty(val)) {
                            List<?> LONG_TIMESTAMP = (List<?>) val;
                            if (!LONG_TIMESTAMP.isEmpty()) {
                                if (LONG_TIMESTAMP.size() == 1 || (LONG_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(LONG_TIMESTAMP.get(1)))) {
                                    Date time1 = new Date(Long.parseLong(LONG_TIMESTAMP.get(0).toString()));
                                    queryWrapper.ge(sFunction, time1);
                                } else if (LONG_TIMESTAMP.size() == 2 && ObjectUtils.isEmpty(LONG_TIMESTAMP.get(0))) {
                                    Date time2 = new Date(Long.parseLong(LONG_TIMESTAMP.get(1).toString()));
                                    queryWrapper.le(sFunction, time2);
                                } else {
                                    Date time1 = new Date(Long.parseLong(LONG_TIMESTAMP.get(0).toString()));
                                    Date time2 = new Date(Long.parseLong(LONG_TIMESTAMP.get(1).toString()));
                                    queryWrapper.between(sFunction, time1, time2);
                                }
                            }
                        }
                        break;
                    default:
                        break;
                }
                field.setAccessible(accessible);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }

        return queryWrapper;
    }


    private boolean isBlank(final CharSequence cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (!Character.isWhitespace(cs.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    private List<Field> getAllFields(Class<?> clazz, List<Field> fields) {
        if (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            getAllFields(clazz.getSuperclass(), fields);
        }
        return fields;
    }

    /***
     * 驼峰命名转为下划线命名
     *
     * @param para
     *        驼峰命名的字符串
     */

    public String humpToUnderline(String para) {
        StringBuilder sb = new StringBuilder(para);
        int temp = 0;//定位
        if (!para.contains("_")) {
            for (int i = 0; i < para.length(); i++) {
                if (Character.isUpperCase(para.charAt(i))) {
                    sb.insert(i + temp, "_");
                    temp += 1;
                }
            }
        }
        return sb.toString();
    }

    public <E> Page<E> toPageDto(List<?> listData, Class<E> cls) {
        if (CollUtil.isEmpty(listData)) {
            return new Page<>();
        }
        boolean isPageData = ClassUtil.getClass(listData).equals(Page.class);
        if (!isPageData) {
            throw new RuntimeException("listData must be Page");
        }
        Page<?> pageData = (Page<?>) listData;
        Page<E> pageResult = BeanUtil.toBean(pageData, Page.class, CopyOptions.create(Page.class, true, "result"));
        pageResult.getResult().addAll(BeanUtil.copyToList(pageData.getResult(), cls));

        return pageResult;
    }

    /**
     * 将方法名称转换为 SFunction
     *
     * @param clazz     类的类型
     * @param fieldName 属性名称
     * @param <T>       类的类型
     * @param <R>       返回值类型
     * @return SFunction
     */
    public static <T, R> SFunction<T, R> fieldNameToSFunction(Class<T> clazz, String fieldName) {
        // 根据属性名获取对应的 Getter 方法名
        final String methodName = "get" + StrUtil.upperFirst(fieldName);
        // 获取指定方法
        Method method = ReflectUtil.getMethod(clazz, methodName);
        if (method == null) {
            throw new RuntimeException("Method not found: " + methodName);
        }
        return t -> {
            try {
                return ReflectUtil.invoke(t, method);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }

}
